/*
 * Copyright 2011 myTDev.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.mytdev.swing.actions;

import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;

/**
 * A Swing abstract action based on resource bundle.
 *
 * @author Yann D'Isanto
 */
abstract class ResourceAbstractAction extends AbstractAction {

    /**
     * This class logger.
     */
    private static final Logger LOG = Logger.getLogger(ResourceAbstractAction.class.getName());

    /**
     * The property prefix used to build key when retreiving the properties
     * resource value.
     */
    private String propertyPrefix;

    /**
     * The resource bundle.
     */
    private ResourceBundle resources;

    /**
     * An common path to load images.
     */
    private String imagesPath;

    /**
     * Instanciates a ResourceAbstractAction using the specified resource bundle.
     * @param resources the resource bundle.
     */
    ResourceAbstractAction(ResourceBundle resources) {
        this(resources, null);
    }

    /**
     * Instanciates a ResourceAbstractAction using the specified resource bundle 
     * and the specified property prefix.
     * @param resources the resource bundle.
     * @param propertyPrefix the property prefix.
     */
    ResourceAbstractAction(ResourceBundle resources, String propertyPrefix) {
        this.resources = resources;
        this.propertyPrefix = propertyPrefix != null ? propertyPrefix : getClass().getSimpleName();
        StringBuilder imagesPathBuilder = new StringBuilder(
                getResource(ActionProperties.IMAGES_PATH, ""));
        if (imagesPathBuilder.length() > 0
                && imagesPathBuilder.charAt(imagesPathBuilder.length() - 1) != '/') {
            imagesPathBuilder.append('/');
        }
        this.imagesPath = imagesPathBuilder.toString();
        String value = getActionPropertyResource(ActionProperties.LABEL);
        if (value != null) {
            putValue(javax.swing.Action.NAME, value);
        }
        value = getActionPropertyResource(ActionProperties.DESCRIPTION);
        if (value != null) {
            putValue(javax.swing.Action.SHORT_DESCRIPTION, value);
            putValue(javax.swing.Action.LONG_DESCRIPTION, value);
        }
        value = getActionPropertyResource(ActionProperties.SHORT_DESCRIPTION);
        if (value != null) {
            putValue(javax.swing.Action.SHORT_DESCRIPTION, value);
        }
        value = getActionPropertyResource(ActionProperties.LONG_DESCRIPTION);
        if (value != null) {
            putValue(javax.swing.Action.LONG_DESCRIPTION, value);
        }
        value = getActionPropertyResource(ActionProperties.MNEMONIC);
        if (value != null) {
            putValue(javax.swing.Action.MNEMONIC_KEY, parseMnemonic(value));
        }
        value = getActionPropertyResource(ActionProperties.ICON);
        if (value != null) {
            Icon icon = loadIcon(value);
            putValue(javax.swing.Action.SMALL_ICON, icon);
            putValue(javax.swing.Action.LARGE_ICON_KEY, icon);
        }
        value = getActionPropertyResource(ActionProperties.SMALL_ICON);
        if (value != null) {
            Icon icon = loadIcon(value);
            putValue(javax.swing.Action.SMALL_ICON, icon);
        }
        value = getActionPropertyResource(ActionProperties.LARGE_ICON);
        if (value != null) {
            Icon icon = loadIcon(value);
            putValue(javax.swing.Action.LARGE_ICON_KEY, icon);
        }
        value = getActionPropertyResource(ActionProperties.KEYSTROKE);
        if (value != null) {
            putValue(javax.swing.Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(value));
        }
        value = getActionPropertyResource(ActionProperties.SELECTED);
        if (value != null) {
            putValue(javax.swing.Action.SELECTED_KEY, Boolean.parseBoolean(value));
        }
        value = getActionPropertyResource(ActionProperties.ENABLED);
        if (value != null) {
            setEnabled(Boolean.parseBoolean(value));
        }

    }

    /**
     * Parse the specified string to a mnemonic int value.
     * Valid values are :
     *  - An int strings in base 10 like "65".
     *  - An int strings in base 16 which must start with "0x" like "0x41".
     *  - An upper case character like "A".
     * @param mnemonic the mnemonic string.
     * @return the mnemonic value.
     * @throws IllegalArgumentException if the specified string is not a valid 
     * mnemonic string.
     */
    private int parseMnemonic(String mnemonic) throws IllegalArgumentException {
        int mnemonicValue = 0;
        if(Pattern.compile("\\d+").matcher(mnemonic).matches()) {
            mnemonicValue = Integer.parseInt(mnemonic);
        } else if(Pattern.compile("[A-Z]").matcher(mnemonic).matches()) {
            mnemonicValue = mnemonic.charAt(0);
        } else if(Pattern.compile("0x([a-fA-F0-9]{2})+").matcher(mnemonic).matches()) {
            mnemonicValue = Integer.parseInt(mnemonic.substring(2), 16);
        } 
        if(mnemonicValue < 'A' || mnemonicValue > 'Z') {
            throw new IllegalArgumentException("invalid mnemonic value");
        }
        return mnemonicValue;
    }
    
    /**
     * Loads and returns an icon according to the specified property.
     * @param iconProperty the icon property value.
     * @return an Icon instance or null if not icon has been loaded.
     */
    private Icon loadIcon(String iconProperty) {
        StringBuilder urlBuilder = new StringBuilder(imagesPath);
        urlBuilder.append(iconProperty);
        URL url = getClass().getClassLoader().getResource(urlBuilder.toString());
        if (url == null && imagesPath.length() > 0) {
            urlBuilder.delete(0, imagesPath.length());
            url = getClass().getClassLoader().getResource(urlBuilder.toString());
        }
        Icon icon = null;
        if (url != null) {
            icon = loadIcon(url);
        } else {
            LOG.log(Level.INFO, "icon url not found: {0}", iconProperty);
        }
        return icon;
    }

    /**
     * Loads and returns an icon according to the specified URL.
     * @param url the icon url.
     * @return an Icon instance or null if not icon has been loaded.
     */
    private static Icon loadIcon(URL url) {
        Icon icon = null;
        try {
            Image image = ImageIO.read(url);
            icon = new ImageIcon(image);
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, "could not load icon {0}", url);
            LOG.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
        }
        return icon;
    }

    /**
     * Returns the resource value for the specified action property.
     * If no resource is found then the null value is returned.
     * @param property the action property.
     * @return the action property resource value.
     */
    private String getActionPropertyResource(String property) {
        return getActionPropertyResource(property, null);
    }

    /**
     * Returns the resource value for the specified action property.
     * If no resource is found then the specified defaultValue is returned.
     * @param property the action property.
     * @param defaultValue the default value.
     * @return the action property resource value.
     */
    private String getActionPropertyResource(String property, String defaultValue) {
        String propertyKey = new StringBuilder(propertyPrefix).append(".").append(property).toString();
        return getResource(propertyKey, defaultValue);
    }

    /**
     * Returns the resource according to the specified key. 
     * If no resource is found then the specified defaultValue is returned.
     * @param key the resource key.
     * @param defaultValue the default value.
     * @return the resource value.
     */
    private String getResource(String key, String defaultValue) {
        String value = defaultValue;
        try {
            value = resources.getString(key);
        } catch (MissingResourceException ex) {
            LOG.log(Level.FINE, "property not found: {0}", key);
            LOG.log(Level.FINER, ex.getLocalizedMessage(), ex);
        }
        return value;
    }
}
